SPB Git forge

spb/cancerindex

Public
37commits 1branches 0releases
2.9 MBsize
maindefault branch
10 days agolast push
TypeScript 97.2% SQL 1.5% CSS 0.6% JavaScript 0.5%
4.2 KB · 105 lines tsx
Raw Blame History
1import type { Metadata } from 'next';2import { notFound, permanentRedirect } from 'next/navigation';3import { loadCancer, TABS, type TabKey } from '@/components/cancer/load';4import { CancerHeader } from '@/components/cancer/header';5import { OverviewTab } from '@/components/cancer/tabs/overview';6import { StatisticsTab } from '@/components/cancer/tabs/statistics';7import { SurvivalTab } from '@/components/cancer/tabs/survival';8import { GenomicsTab } from '@/components/cancer/tabs/genomics';9import { EvidenceTab } from '@/components/cancer/tabs/evidence';10import { DrugsTab } from '@/components/cancer/tabs/drugs';11import { TrialsTab } from '@/components/cancer/tabs/trials';12import { ResearchTab } from '@/components/cancer/tabs/research';13import { RankingsTab } from '@/components/cancer/tabs/rankings';14import { SourcesTab } from '@/components/cancer/tabs/sources';15import { jsonLd, medicalConditionLd } from '@/lib/seo';16import { str, int, type SP } from '@/lib/search-params';17import { truncate } from '@/lib/format';1819export const revalidate = 3600;20export const dynamicParams = true;2122type Params = { slug: string; tab?: string[] };2324function tabKey(tab: string[] | undefined): TabKey | null {25  const t = tab?.[0] ?? 'overview';26  if (tab && tab.length > 1) return null;27  return TABS.some((x) => x.key === t) ? (t as TabKey) : null;28}2930export async function generateMetadata({ params }: { params: Promise<Params> }): Promise<Metadata> {31  const { slug, tab } = await params;32  const b = await loadCancer(slug);33  if (!b) return { title: 'Not found' };34  const key = tabKey(tab) ?? 'overview';35  const label = TABS.find((t) => t.key === key)?.label ?? '';36  const desc = b.cancer.description ? truncate(b.cancer.description, 160) : `${b.cancer.canonical_name} (${b.cancer.id}): taxonomy, statistics, genomics, evidence, drugs, trials, literature and rankings with full provenance.`;37  return {38    title: key === 'overview' ? b.cancer.canonical_name : `${b.cancer.canonical_name} — ${label}`,39    description: desc,40    alternates: { canonical: key === 'overview' ? `/cancer/${b.cancer.slug}` : `/cancer/${b.cancer.slug}/${key}` },41  };42}4344export default async function CancerPage({ params, searchParams }: { params: Promise<Params>; searchParams: Promise<SP> }) {45  const { slug, tab } = await params;46  const key = tabKey(tab);47  if (!key) notFound();48  const b = await loadCancer(slug);49  if (!b) notFound();50  if (b.cancer.slug !== slug) permanentRedirect(key === 'overview' ? `/cancer/${b.cancer.slug}` : `/cancer/${b.cancer.slug}/${key}`);5152  let body: React.ReactNode;53  switch (key) {54    case 'statistics':55      body = <StatisticsTab b={b} />;56      break;57    case 'survival':58      body = <SurvivalTab b={b} />;59      break;60    case 'genomics': {61      const sp = await searchParams;62      body = <GenomicsTab b={b} cohort={str(sp, 'cohort') || null} />;63      break;64    }65    case 'evidence': {66      const sp = await searchParams;67      body = <EvidenceTab b={b} evPage={int(sp, 'evPage', 1, 1, 100_000)} />;68      break;69    }70    case 'drugs': {71      const sp = await searchParams;72      body = <DrugsTab b={b} jurisdiction={str(sp, 'jurisdiction') || null} aPage={int(sp, 'aPage', 1, 1, 100_000)} />;73      break;74    }75    case 'trials': {76      const sp = await searchParams;77      body = <TrialsTab b={b} status={str(sp, 'status')} phase={str(sp, 'phase')} page={int(sp, 'page', 1, 1, 100_000)} />;78      break;79    }80    case 'research': {81      const sp = await searchParams;82      body = <ResearchTab b={b} pPage={int(sp, 'pPage', 1, 1, 100_000)} />;83      break;84    }85    case 'rankings':86      body = <RankingsTab b={b} />;87      break;88    case 'sources':89      body = <SourcesTab b={b} />;90      break;91    default:92      body = <OverviewTab b={b} />;93  }9495  const ld = medicalConditionLd({ slug: b.cancer.slug, canonicalName: b.cancer.canonical_name, description: b.cancer.description, aliases: b.aliases.filter((a) => a.alias_type !== 'preferred').map((a) => a.alias), codes: b.codes });9697  return (98    <article>99      <script type="application/ld+json" dangerouslySetInnerHTML={{ __html: jsonLd(ld) }} />100      <CancerHeader b={b} tab={key} />101      <div className="pt-6">{body}</div>102    </article>103  );104}105